"""Cython-based Python Module"""
import re
from pathlib import Path
from packaging.specifiers import SpecifierSet
from packaging.version import parse as parse_version

from buildutils import (get_command_output, which, multi_glob, logger,
                        get_pip_install_location, setup_python_env)

Import('env', 'build', 'install')

localenv = env.Clone()

dataFiles = []
for yaml in multi_glob(localenv, "#data", "yaml"):
    dataFiles.append(build(
        localenv.Command(f"cantera/data/{yaml.name}", yaml.abspath,
                         Copy("$TARGET", "$SOURCE"))))

if localenv["example_data"]:
    for yaml in multi_glob(localenv, "#data/example_data", "yaml"):
        dataFiles.append(build(
            localenv.Command(f"cantera/data/example_data/{yaml.name}", yaml.abspath,
                             Copy("$TARGET", "$SOURCE"))))

# Install Python samples
install(localenv.RecursiveInstall, "$inst_sampledir/python", "#samples/python")

setup_python_env(localenv)
logger.info(f"Using setuptools version {localenv['setuptools_version']}")
# Python module shouldn't explicitly link to Python library (added in other cases to
# support Python-based extensions), except when using MinGW
if localenv["toolchain"] != "mingw":
    localenv["LIBS"] = [lib for lib in localenv["LIBS"] if not lib.startswith("python")]

setup_cfg = localenv.SubstFile("setup.cfg", "setup.cfg.in")
readme = localenv.Command("README.rst", "#README.rst", Copy("$TARGET", "$SOURCE"))
license = localenv.Command("LICENSE.txt", "#build/ext/LICENSE.txt",
                           Copy("$TARGET", "$SOURCE"))
localenv.Depends(license, localenv["license_target"])

directives = {"binding": True}
if env["coverage"]:
    directives["linetrace"] = True
    localenv.Append(CPPDEFINES={"CYTHON_TRACE": 1})

# Build the Python module
cython_obj = []
for pyxfile in multi_glob(localenv, "cantera", "pyx"):
    if pyxfile.name == "_utils.pyx":
        # Define GIT_COMMIT only in _utils.pyx to avoid unnecessary recompilations
        cython_env = localenv.Clone()
        cython_env.Append(CPPDEFINES={'GIT_COMMIT': '\\"{0}\\"'.format(env['git_commit'])})
    else:
        cython_env = localenv

    cython_output = f"cantera/{pyxfile.name.replace('.pyx', '.cpp')}"
    if pyxfile.name == "delegator.pyx":
        cython_output = [cython_output, "cantera/delegator.h"]

    cythonized = cython_env.Command(
         cython_output, pyxfile,
         f'''${{python_cmd}} -c "import setuptools; import Cython.Build; Cython.Build.cythonize(r'${{SOURCE}}', compiler_directives={directives!r})"'''
    )
    for pxd in multi_glob(cython_env, "cantera", "pxd"):
        cython_env.Depends(cythonized, pxd)

    obj = cython_env.SharedObject(
        f"#build/temp-py/{pyxfile.name.split('.')[0]}", cythonized[0])
    cython_obj.append(obj)

copy_header = localenv.Command('#src/extensions/delegator.h', '#build/python/cantera/delegator.h',
                               Copy('$TARGET', '$SOURCE'))
ext_manager = localenv.SharedObject("#src/extensions/PythonExtensionManager.cpp")

cython_obj.extend(ext_manager)
env.Depends(ext_manager, copy_header)

module_ext = localenv["py_module_ext"]
ext = localenv.LoadableModule(f"cantera/_cantera{module_ext}",
                              cython_obj, LIBPREFIX="", SHLIBSUFFIX=module_ext,
                              SHLIBPREFIX="", LIBSUFFIXES=[module_ext])

build_cmd = ("$python_cmd_esc -m pip wheel -v --no-build-isolation --no-deps "
             "--wheel-dir=build/python/dist build/python")
# We've already warned that this Python version might be unsupported, so disable
# pip's check for a correct version of Python.
if parse_version(localenv["py_version_short"]) >= env["python_max_version"]:
    build_cmd += " --ignore-requires-python"

# Newer setuptools versions always lowercase the package name
spec1 = SpecifierSet(">=75.8.1")
spec2 = SpecifierSet("~=75.3.1")  # change backported to this branch
if localenv["setuptools_version"] in spec1 or localenv["setuptools_version"] in spec2:
    pkg_name = "cantera"
else:
    pkg_name = "Cantera"

wheel_name = (pkg_name + "-${cantera_version}-cp${py_version_nodot}"
              "-cp${py_version_nodot}-${py_plat}.whl")
mod = build(localenv.Command(f"#build/python/dist/{wheel_name}", "setup.cfg",
                             build_cmd))
env['python_module'] = mod
env['python_extension'] = ext

localenv.Depends(mod, [ext, dataFiles, setup_cfg, readme, license,
                       "setup.py", "pyproject.toml",
                       "cantera/test/README.txt", "cantera/examples/README.txt"])

if env['OS'] == 'Windows':
    # On Windows, the cantera library directory is likely not to be on the path.
    # However, Windows does search the directory containing a library (i.e. the
    # Python extension module) for DLL dependencies.
    dll = [f for f in localenv['cantera_shlib'] if f.name.endswith('.dll')][0]
    copy_dll = localenv.Command(f'cantera/{dll.name}', dll, Copy("$TARGET", "$SOURCE"))
    localenv.Depends(ext, copy_dll)

    # If compiling with MinGW, there are some system libraries that also seem
    # to need to be installed alongside the Python extension -- elsewhere on the path
    # does not appear to work.
    if env['toolchain'] == 'mingw':
        mingw_dir = Path(which(env.subst('$CXX'))).parent
        prefixes = ['libgcc', 'libstdc++', 'libwinpthread']
        for lib in mingw_dir.glob('*.dll'):
            if any(lib.name.startswith(prefix) for prefix in prefixes):
                copy_lib = localenv.Command(f'cantera/{lib.name}', str(lib),
                                            Copy('$TARGET', '$SOURCE'))
                localenv.Depends(mod, copy_lib)

else:
    localenv.Depends(ext, localenv['cantera_shlib'])

for f in (multi_glob(localenv, 'cantera', 'py', 'pyi') +
          multi_glob(localenv, 'cantera/*', 'py', 'pyi')):
    localenv.Depends(mod, f)
localenv.Depends(mod, 'cantera/py.typed')

units = localenv.UnitsInterfaceBuilder(
    "cantera/with_units/solution.py",
    "cantera/with_units/solution.py.in",
)
localenv.Depends(mod, units)

# Determine installation path and install the Python module
install_cmd = ["$python_cmd_esc", "-m", "pip", "install"]
user_install = False
python_prefix = None
if localenv['python_prefix'] == 'USER':
    # Install to the OS-dependent user site-packages directory
    install_cmd.append("--user")
    user_install = True
elif localenv["python_prefix"]:
    # A specific location for the Cantera python module has been given
    install_cmd.append(f"--prefix={localenv.subst('$python_prefix')}")
    python_prefix = localenv.subst("$python_prefix")
elif not env["default_prefix"]:
    install_cmd.append(f"--prefix={env['prefix']}")
    python_prefix = env["prefix"]

# Check for existing Python module installation. Allow pip to remove an existing
# installation only if we're installing to the same location. Also disable
# uninstallation if we're installing to a staging directory.
if env["stage_dir"]:
    install_cmd.append("--ignore-installed")
else:
    info = get_command_output(localenv["python_cmd"], "-m", "pip", "show", "cantera",
                              ignore_errors=True)

    if user_install:
        test_prefix = Path(localenv["user_site_packages"]).parents[2]
    elif python_prefix is None:
        test_prefix = Path(localenv["site_packages"][0]).parents[2]
    else:
        test_prefix = Path(python_prefix)

    match = re.search(r"Location: (.*)\n", info, re.MULTILINE)
    existing_prefix = Path(match.group(1)).parents[2] if match else None
    if existing_prefix and existing_prefix != test_prefix:
        install_cmd.append("--ignore-installed")

if env["stage_dir"]:
    # Get the absolute path to the stage directory. If the stage directory is a relative
    # path, consider it to be relative to the root of the Cantera source directory.
    stage_dir = Path(env["stage_dir"])
    if not stage_dir.is_absolute():
        stage_dir = Path(Dir("#").abspath) / stage_dir

    install_cmd.append(f"--root={stage_dir.resolve()}")

install_cmd.extend(("--no-build-isolation", "--no-deps", "-v", "--force-reinstall",
                    "$SOURCE"))

mod_inst = install(localenv.Command, "dummy", mod, " ".join(install_cmd))
env["install_python_action"] = mod_inst
install_locs = get_pip_install_location(localenv["python_cmd"], user_install,
                                        python_prefix)
env["python_module_loc"] = Path(install_locs["platlib"]).as_posix()
env["ct_pyscriptdir"] = Path(install_locs["scripts"]).as_posix()
